package com.sh.cloudrender.domian.service.kube;

import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.apis.CustomObjectsApi;
import io.kubernetes.client.openapi.models.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class NodeService {

    @Value("${engine.clusterExternalHost}")
    private String clusterExternalHost;

    @Autowired
    private CoreV1Api coreV1Api;

    @Autowired
    private CustomObjectsApi customObjectsApi;

    public String getClusterExternalHost() {
        return clusterExternalHost;
    }

    public void labelNodesWithGPU() {
        String osName = System.getProperty("os.name").toLowerCase();
        String commandPrefix = osName.contains("win") ? "cmd.exe /c " : "/bin/bash -c ";

        try {
            List<String> nodes = executeCommand(commandPrefix + "kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{\"\n\"}{end}'");
            for (String node : nodes) {
                String gpuInfo = executeCommandAndGetOutput(commandPrefix + "kubectl describe node " + node + " | grep -iE 'nvidia|gpu'");
                if (!gpuInfo.isEmpty()) {
                    log.info("Node:{} has GPU. Adding label...", node);
                    executeCommand(commandPrefix + "kubectl label nodes " + node + " kubernetes.io/gpu=true");
                }
            }
        } catch (IOException | InterruptedException e) {
            log.error("label nodes with gpu error", e);
        }
    }

    public boolean checkResourceAvailable(int cpu, int memory, boolean enableGpu) {
        Map<String, BigDecimal> apply = new HashMap<>();
        apply.put("cpu", new BigDecimal(cpu));
        apply.put("memory", new BigDecimal(memory));
//        apply.put("gpu", enableGpu ? BigDecimal.ONE : BigDecimal.valueOf(-1));
        // TODO 服务器目前没有GPU资源，恒等于-1
        apply.put("gpu", BigDecimal.valueOf(-1));
        Map<String, BigDecimal> nodesCapacity = getClusterNodesResCapacity();
        Map<String, BigDecimal> specified = getNodesSpecifiedResources();

        for (String key : nodesCapacity.keySet()) {
            BigDecimal capacity = nodesCapacity.get(key);
            BigDecimal specify = specified.get(key);
            BigDecimal free = capacity.subtract(specify);
            if (free.compareTo(apply.get(key)) < 0) {
                return false;
            }
        }
        return true;
    }

    private Map<String, BigDecimal> getClusterNodesResCapacity() {
        V1NodeList nodeList = null;
        try {
            nodeList = coreV1Api.listNode(null, null, null, null, null, null, null, null, null, null);
        } catch (ApiException e) {
            return null;
        }

        if (nodeList == null) {
            return null;
        }

        List<V1Node> nodes = nodeList.getItems();
        if (CollectionUtils.isEmpty(nodes)) {
            return null;
        }
        Map<String, BigDecimal> nodesCapacity = new HashMap<>();
        nodesCapacity.put("cpu", BigDecimal.ZERO);
        nodesCapacity.put("memory", BigDecimal.ZERO);
        nodesCapacity.put("gpu", BigDecimal.ZERO);

        for (V1Node node : nodes) {
            BigDecimal availableCpus = node.getStatus().getAllocatable().get("cpu").getNumber();
            BigDecimal availableMemory = node.getStatus().getAllocatable().get("memory").getNumber().divide(new BigDecimal(1 << 30), 2, RoundingMode.HALF_UP);
            BigDecimal availableGpus = node.getStatus().getAllocatable().get("nvidia.com/gpu") != null ? node.getStatus().getAllocatable().get("nvidia.com/gpu").getNumber() : BigDecimal.ZERO;

            nodesCapacity.put("cpu", nodesCapacity.get("cpu").add(availableCpus));
            nodesCapacity.put("memory", nodesCapacity.get("memory").add(availableMemory));
            nodesCapacity.put("gpu", nodesCapacity.get("gpu").add(availableGpus));
        }

        return nodesCapacity;
    }

    private Map<String, BigDecimal> getNodesSpecifiedResources() {
        Map<String, BigDecimal> specifiedRes = new HashMap<>();
        specifiedRes.put("cpu", BigDecimal.ZERO);
        specifiedRes.put("memory", BigDecimal.ZERO);
        specifiedRes.put("gpu", BigDecimal.ZERO);

        V1PodList list = null;
        try {
            list = coreV1Api.listPodForAllNamespaces(null, null, null, null, null, null, null, null, null, null);
        } catch (ApiException e) {
            throw new RuntimeException(e);
        }

        for (V1Pod item : list.getItems()) {
            for (V1Container container : item.getSpec().getContainers()) {
                if (container.getResources().getRequests() != null) {
                    specifiedRes.put("cpu", specifiedRes.get("cpu").add(container.getResources().getRequests().get("cpu").getNumber()));
                    if (container.getResources().getRequests().get("memory") != null) {
                        specifiedRes.put("memory", specifiedRes.get("memory").add(container.getResources().getRequests().get("memory").getNumber().divide(new BigDecimal(1 << 30))));
                    }
                }
            }
        }

        return specifiedRes;
    }

    private Map<String, BigDecimal> getNodesResourceUsage(String namespace) {
        // 获取节点上的所有Pod的资源使用情况
        Object metrics = null;
        try {
            metrics = customObjectsApi.listNamespacedCustomObject("metrics.k8s.io", "v1beta1", namespace, "pods", null, null, null, null, null, null, null, null, null, false);
        } catch (ApiException e) {
            log.error("get cluster metrics error", e);
            return null;
        }

        if (metrics == null) {
            return null;
        }


        Map<String, BigDecimal> nodesUsage = new HashMap<>();
        nodesUsage.put("cpu", BigDecimal.ZERO);
        nodesUsage.put("memory", BigDecimal.ZERO);

        if (metrics instanceof Map) {
            Map<String, Object> metricsMap = (Map<String, Object>) metrics;
            if (metricsMap.containsKey("items")) {
                List<Map<String, Object>> items = (List) metricsMap.get("items");
                for (Map<String, Object> item : items) {
                    List<Map<String, Object>> containers = (List<Map<String, Object>>) item.get("containers");
                    for (Map<String, Object> container : containers) {
                        Map<String, Object> usageMap = (Map<String, Object>) container.get("usage");
                        BigDecimal tmpmemoryUsage = new BigDecimal(usageMap.get("memory").toString().replaceAll("[^\\d]", ""));
                        BigDecimal memoryUsage = tmpmemoryUsage.divide(new BigDecimal(1 << 30));
                        BigDecimal tmpcpuUsage = new BigDecimal(usageMap.get("cpu").toString().replaceAll("[^\\d]", ""));
                        BigDecimal cpuUsage = tmpcpuUsage.divide(new BigDecimal(1000000000));
                        nodesUsage.put("cpu", nodesUsage.get("cpu").add(cpuUsage));
                        nodesUsage.put("memory", nodesUsage.get("memory").add(memoryUsage));
                    }
                }
            }
        }

        return nodesUsage;
    }

    private List<String> executeCommand(String command) throws IOException, InterruptedException {
        List<String> outputLines = new ArrayList<>();
        Process process = Runtime.getRuntime().exec(command);
        process.waitFor();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                outputLines.add(line);
            }
        }
        return outputLines;
    }

    private String executeCommandAndGetOutput(String command) throws IOException, InterruptedException {
        StringBuilder output = new StringBuilder();
        Process process = Runtime.getRuntime().exec(command);
        process.waitFor();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                output.append(line).append("\n");
            }
        }
        return output.toString();
    }
}
